# -*- coding: utf-8 -*-
# created on 2016/7/20


from mathsolver.functions.base import *
from sympy import Interval, S, sympify, oo
from mathsolver.functions.fangcheng import FangChenBuDengShi002, FangChenBuDengShi001
from mathsolver.functions.mathematica.mathematicaSolve import MathematicaSolve
from mathsolver.functions.jihe.jihe_operate import JiHeOperate
import itertools


# 根据集合关系求集合、求参
class JiHeSolve(BaseFunction):
    def solve_empty(self, left):  # left = ∅
        left_set = self.search(left)
        assert isinstance(left_set, BaseInnerSet)
        left_v = left_set.value["value"]

        if isinstance(left_v, list):
            if any([len(sympify(v).free_symbols) > 0 for v in left_v]):
                self.steps.append(["当%s=∅" % left, "无解"])
        elif isinstance(left_v, Interval) and left_v.free_symbols:
            a, b, _, _ = left_v.args
            ineq = [a, ">", b]
            self.steps.append(["因为%s=∅" % left, self.output_eq(ineq)])
            self.output.append(BaseCondition(BaseIneq(ineq), "ineq", True))
        else:
            raise ValueError("unsupport %s" % type(left_v))

    def solve_unival(self, left, right):
        left_set = self.search(left)
        right_set = self.search(right)
        assert isinstance(left_set, BaseInnerSet) and isinstance(right_set, BaseInnerSet)

        left_v = left_set.value["value"]
        right_v = right_set.value["value"]

        if isinstance(left_v, Interval) and isinstance(right_v, Interval):
            da = left_v
            db = right_v
            ineqs = []

            if type(da) == Union:
                a_right = da.args[1]
                a_left = da.args[0]
                if a_left.right_open & db.left_open:
                    if a_right.left_open & db.right_open:
                        ineqs.append([db.end, '>', a_right.start])
                        ineqs.append([db.start, '<', a_left.end])
                    else:
                        ineqs.append([db.end, '>=', a_right.start])
                        ineqs.append([db.start, '<', a_left.end])
                else:
                    if a_right.left_open & db.right_open:
                        ineqs.append([db.end, '>', a_right.start])
                        ineqs.append([db.start, '<=', a_left.end])
                    else:
                        ineqs.append([db.end, '>=', a_right.start])
                        ineqs.append([db.start, '<=', a_left.end])

            elif type(db) == Union:
                da, db = db, da
                a_right = da.args[1]
                a_left = da.args[0]
                if a_left.right_open & db.left_open:
                    if a_right.left_open & db.right_open:
                        ineqs.append([db.end, '>', a_right.start])
                        ineqs.append([db.start, '<', a_left.end])
                    else:
                        ineqs.append([db.end, '>=', a_right.start])
                        ineqs.append([db.start, '<', a_left.end])
                else:
                    if a_right.left_open & db.right_open:
                        ineqs.append([db.end, '>', a_right.start])
                        ineqs.append([db.start, '<=', a_left.end])
                    else:
                        ineqs.append([db.end, '>=', a_right.start])
                        ineqs.append([db.start, '<=', a_left.end])

            else:
                if da.start == -oo:
                    if da.right_open & db.left_open:
                        ineqs.append([db.start, '<', da.end])
                    else:
                        ineqs.append([db.start, '<=', da.end])

                else:
                    da, db = db, da
                    if da.right_open & db.left_open:
                        ineqs.append([db.start, '<', da.end])
                    else:
                        ineqs.append([db.start, '<=', da.end])

            stepsolver = MathematicaSolve().solver(BaseIneqs(ineqs))
            self.output.extend(stepsolver.output)

        else:
            raise ValueError("unsupported type %s %s" % (type(left_v), type(right_v)))

    def _solve_inter_empty(self, left_v, right_v):
        ineqs = []
        for interval in right_v._args:
            a, b = interval.args[:2]
            if a.is_infinite is True:
                ineq = [b, "<", left_v.args[0]]
                ineqs.append(ineq)
            elif b.is_infinite is True:
                ineq = [a, ">", left_v.args[1]]
                ineqs.append(ineq)

        stepsolver = FangChenBuDengShi002().solver(BaseIneqs(ineqs))
        self.output.append(stepsolver.output[0])

    def solve_inter_empty(self, left, right):  # A ∩ B = ∅
        left_set = self.search(left)
        right_set = self.search(right)
        assert isinstance(left_set, BaseInnerSet) and isinstance(right_set, BaseInnerSet)

        left_v = left_set.value["value"]
        right_v = right_set.value["value"]

        if isinstance(left_v, Interval) and isinstance(right_v, Interval):
            interval = right_v
            if not (left_v.args[0].is_finite is False) and not (right_v.args[1].is_finite is False):
                ineq = BaseIneq([left_v.args[0], ">", interval.args[1]])
                self.output.append(BaseCondition(ineq, "ineq", True))

            if not (left_v.args[1].is_finite is False) and not (right_v.args[0].is_finite is False):
                ineq = BaseIneq([left_v.args[1], "<", interval.args[0]])
                self.output.append(BaseCondition(ineq, "ineq", True))
        elif isinstance(left_v, Interval) and isinstance(right_v, Union):
            self._solve_inter_empty(left_v, right_v)

        elif isinstance(left_v, Union) and isinstance(right_v, Interval):
            self._solve_inter_empty(right_v, left_v)
        else:
            raise ValueError("unsupported type %s %s" % (type(left_v), type(right_v)))

    def solve_equal(self, left, right):  # A = B
        self.left = left
        self.right = right
        # self.output.append(BaseCondition(BaseSeteq([left, "=", "∅"]), "seteq", True))
        left_set = self.search(left)
        right_set = self.search(right)
        assert isinstance(left_set, BaseInnerSet) and isinstance(right_set, BaseInnerSet)

        left_v = left_set.value["value"]
        right_v = right_set.value["value"]
        if isinstance(left_v, list) and isinstance(right_v, list):
            self.solve_list(left_v, right_v, func=lambda a, b: a == b)

    def solve_assign(self, left, right):  # P=M∩N
        stepsolver = JiHeOperate(known=self.known).solver(BaseSetOp(right))
        value = stepsolver.output[0]
        value.value["name"] = left
        self.steps.extend(stepsolver.steps)
        self.output.append(value)

    def solve_unempty(self, left, right):
        ineqs = []
        cond = {}
        for name in [left, right]:
            value = self.search(name).value["value"]
            sym = self.search(name).value["var"]
            if isinstance(value, BaseIneq):
                ineqs.append(value.value)
            else:
                cond[sympify(sym)] = value

        return MathematicaSolve().solver(BaseIneqs(ineqs), BaseVariables(["a", "x"]), cond)

    def solve_list(self, left_v, right_v, func):
        if isinstance(left_v, list):
            left_v = FiniteSet(*left_v)

        if isinstance(right_v, list):
            right_v = FiniteSet(*right_v)

        if len(left_v) <= len(right_v):
            candidates = [[(k, v) if sympify(k).free_symbols else (v, k) for k, v in zip(xx, left_v)] for xx in
                          itertools.permutations(right_v, len(left_v))]
        else:
            candidates = [zip(xx, right_v) for xx in itertools.permutations(left_v, len(right_v))]

        candidates = [dict(can) for can in candidates if
                      all([BaseEq([k, v]).free_symbols() or str(k) == str(v) for k, v in can])]
        exprs = []
        for cand in candidates:
            new_left_v = [cand[v] if v in cand else v for v in left_v]
            new_right_v = [cand[v] if v in cand else v for v in right_v]
            if len(set(new_left_v)) != len(new_left_v) or len(set(new_right_v)) != len(new_right_v):
                continue

            new_left_v = set(new_left_v)
            new_right_v = set(new_right_v)
            # if new_right_v.difference(right_v):
            #     name = self.right
            #     value = new_right_v
            # else:
            #     name = self.left
            #     value = new_left_v

            ineqs = []
            for value in [new_left_v, new_right_v]:
                if FiniteSet(*value).free_symbols:
                    temp_variable = [v for v in value if sympify(v).free_symbols]
                    temp_value = [v for v in value if not sympify(v).free_symbols]
                    ineqs.extend([[t1, "!=", t2] for t1 in temp_variable for t2 in temp_value])

            if func(new_left_v, new_right_v):  # len(new_left_v.difference(new_right_v)) == 0:
                eqs = [[k, v] for k, v in cand.items() if str(k) != str(v)]
                exprs.append(BaseIneqs(ineqs + eqs))
                # if len(eqs) == 1:
                #     if solveset(Eq(*eqs[0])):
                #         eq = BaseEq(eqs[0])
                #         eq.is_or = True
                #         self.output.append(eq)
                #         self.steps.append(["当%s = {%s}" % (name, ",".join([str(v) for v in value])), self.output_eq(eqs[0])])
                # else:
                #     stepsolver = jiefangchenzu().solver(BaseEqs(eqs))
                #     if stepsolver.output and stepsolver.output[0].value:
                #         self.output.append(BaseEqs(eqs))
                #         self.steps.append(["当%s = {%s}" % (name, ",".join([str(v) for v in value])), self.output_eqs(eqs)])

        self.append(MathematicaSolve().solver(BaseOr(exprs)))
        assert self.output, "no output"
        return

    @staticmethod
    def _solve_child_unival(qujian1, qujian2):
        if isinstance(qujian1, Set):
            qujian1 = {S.Reals: qujian1}

        if isinstance(qujian2, Set):
            qujian2 = {S.Reals: qujian2}

        answers = []
        for q1, q2 in itertools.product(qujian1.items(), qujian2.items()):
            a_interval, da = q1
            b_interval, db = q2
            if da in db:
                if not isinstance(a_interval, Set):
                    answers.append(FiniteSet(a_interval))
                else:
                    answers.append(a_interval)
            else:
                ineqs = []
                if (da.left_open is False) & (db.left_open is True):
                    if (da.right_open is False) & (db.right_open is True):
                        ineqs.append([da.start, ">", db.start])
                        ineqs.append([da.end, "<", db.end])
                    else:
                        ineqs.append([da.start, ">", db.start])
                        ineqs.append([da.end, "<=", db.end])
                elif (da.right_open is False) & (db.right is True):
                    ineqs.append([da.start, ">=", db.start])
                    ineqs.append([da.end, "<", db.end])
                else:
                    ineqs.append([da.start, ">=", db.start])
                    ineqs.append([da.end, "<=", db.end])

                ineqs = BaseIneqs(ineqs)
                sym = list(ineqs.free_symbols())[0]
                stepsolver = MathematicaSolve().solver(ineqs, BaseVariable(sym),
                                                       {sym: a_interval.intersection(b_interval)})
                if isinstance(stepsolver.output[0], BaseVariable):
                    answers.append(S.Reals)
                else:
                    answers.append(stepsolver.output[0].value.values()[0])
        return Union(answers)

    @staticmethod
    def _solve_child(left_value, right_value, var, xx):
        a = [v for v in var if str(v) != str(xx)][0]
        x_index = var.index(xx)
        a_index = var.index(a)

        answers = []
        for item in left_value:
            x_interval = item[x_index]
            a_interval = item[a_index]
            da = x_interval
            db = right_value
            if da in db:
                if not isinstance(a_interval, Set):
                    answers.append(FiniteSet(a_interval))
                else:
                    answers.append(a_interval)
            else:
                ineqs = []
                if (da.left_open is False) & (db.left_open is True):
                    if (da.right_open is False) & (db.right_open is True):
                        ineqs.append([da.start, ">", db.start])
                        ineqs.append([da.end, "<", db.end])
                    else:
                        ineqs.append([da.start, ">", db.start])
                        ineqs.append([da.end, "<=", db.end])
                elif (da.right_open is False) & (db.right is True):
                    ineqs.append([da.start, ">=", db.start])
                    ineqs.append([da.end, "<", db.end])
                else:
                    ineqs.append([da.start, ">=", db.start])
                    ineqs.append([da.end, "<=", db.end])
                stepsolver = MathematicaSolve().solver(BaseIneqs(ineqs), None, {a: a_interval})
                answers.append(stepsolver.output[0].value.values()[0])
        return {a: Union(answers)}

    def solve_child(self, left, right):  # left ⊆ right
        self.left = left
        self.right = right
        # self.output.append(BaseCondition(BaseSeteq([left, "=", "∅"]), "seteq", True))
        left_set = self.search(left)
        right_set = self.search(right)
        assert isinstance(left_set, BaseInnerSet) and isinstance(right_set, BaseInnerSet)

        left_v = left_set.value["value"]
        right_v = right_set.value["value"]

        if isinstance(right, EmptySet):
            self.solve_empty(left)

        elif isinstance(left_v, Interval) and isinstance(right_v, Interval):

            interval = right_v
            ineqs = []
            if not (left_v.args[0].is_finite is False) and not (right_v.args[0].is_finite is False):
                ineqs.append([left_v.args[0], ">=", interval.args[0]])

            if not (left_v.args[1].is_finite is False) and not (right_v.args[1].is_finite is False):
                ineqs.append([left_v.args[1], "<=", interval.args[1]])

            if len(ineqs) > 1:
                stepsolver = FangChenBuDengShi002().solver(BaseIneqs(ineqs))
            else:
                stepsolver = FangChenBuDengShi001().solver(BaseIneq(ineqs[0]))

            self.output.extend(stepsolver.output)

        elif isinstance(left_v, (FiniteSet, list)) and isinstance(right_v, (FiniteSet, list)):
            self.solve_list(left_v, right_v, func=lambda a, b: len(a.difference(b)) == 0)

        elif isinstance(left_v, Set) and isinstance(right_v, BaseIneqs):
            symbols = [sym for sym in right_v.free_symbols() if str(sym) != left_set.value["var"]]
            assert len(symbols) == 1
            self.output.append(BaseSymbolValue({symbols[0]: left_v}))
        elif isinstance(right_v, Set) and isinstance(left_v, BaseIneqs):
            symbols = [sym for sym in left_v.free_symbols() if str(sym) != right_v.value["var"]]
            assert len(symbols) == 1
            self.output.append(BaseSymbolValue({symbols[0]: right_v}))
        elif isinstance(left_v, FiniteSet) and isinstance(right_v, Interval):
            var = left_set.value['var']
            xx = right_set.value['var'] if right_set.value['var'] else Symbol('x')
            left_value = left_set.value['value']
            self.output.append(BaseSymbolValue(self._solve_child(left_value, right_v, var, xx)))

        else:
            raise ValueError("unexpected left_v %s right_v %s" % (left_v, right_v))

    def solve_inter(self, left, right, value):
        self.left = left
        self.right = right

        left_set = self.search(left)
        right_set = self.search(right)

        left_v = left_set.value["value"]
        right_v = right_set.value["value"]

        if isinstance(left_v, list):
            left_v = FiniteSet(*left_v)

        if isinstance(right_v, list):
            right_v = FiniteSet(*right_v)

        if isinstance(left_v, Interval) and isinstance(right_v, Interval):
            if len(value["conditions"]) == 1 and isinstance(value["conditions"][0], str):
                v = sympify(value["conditions"][0])
                for item in [left_v, right_v]:
                    if item.free_symbols:
                        for arg in item.args[:2]:
                            if arg.free_symbols:
                                eq = BaseEq([arg, v])
                                self.steps.append(["因为%s∩%s={%s}" % (left, right, new_latex(v)), eq.printing()])
                                self.output.append(BaseCondition(eq, "eq"))
                                return
        elif isinstance(left_v, FiniteSet) and isinstance(right_v, FiniteSet):
            if 'set' in value["conditions"][0]:
                target = [sympify(v) for v in value["conditions"][0]['set']]
            else:
                target = [sympify(v) for v in value["conditions"]]
            variable = [v for v in set(left_v + right_v) if sympify(v).free_symbols]
            left_v = FiniteSet(*[v for v in left_v if not v.free_symbols])
            right_v = FiniteSet(*[v for v in right_v if not v.free_symbols])
            self.solve_list(left_v.intersect(right_v).union(FiniteSet(*variable)), target, lambda a, b: a == b)
        else:
            raise ValueError

    def fake_return(self, *args):
        if all([isinstance(arg, list) for arg in args]):
            symbols = set(
                [sym for arg in args for item in arg for sym in sympify(item).free_symbols if str(sym) != "x"])
        else:
            symbols = set([sym for arg in args for sym in self.search(arg).free_symbols() if str(sym) != "x"])
        self.output.append(BaseSymbolValue(dict([(sym, 1) for sym in symbols])))

    def solver(self, *args):
        left, op, right = args[0].value
        if op == "=" and isinstance(left, str) and isinstance(right, str):  # U = R
            if right == "R":
                self.output.append(BaseInnerSet({"name": left, "var": "", "value": S.Reals}))
            elif right == "∅":
                try:
                    self.solve_empty(left)
                except Exception:
                    self.fake_return(left)
                self.label.add("集合（不）为空集求参数")

            else:
                self.solve_equal(left, right)
                self.label.add("集合相等求参数")

        elif op == "=" and isinstance(left, list) and isinstance(right, str):  # A∩B = R
            if len(left) == 3:
                a, op, b = left
                if (a == right and op == "∩") or (b == right and op == "∪"):
                    seteq = BaseSetEq([a, "⊆", b])
                    self.steps.append(["因为%s%s%s" % (a, op, b), "所以%s ⊆ %s" % (a, b)])
                    self.output.append(seteq)
                    self.label.add("子集与交集、并集运算的转换求参数")
                elif (b == right and op == "∩") or (a == right and op == "∪"):
                    seteq = BaseSetEq([b, "⊆", a])
                    self.steps.append(["因为%s%s%s" % (a, op, b), "所以%s ⊆ %s" % (b, a)])
                    self.output.append(seteq)
                    self.label.add("子集与交集、并集运算的转换求参数")
                elif op == "∩" and right in ["ph", "∅"]:
                    self.solve_inter_empty(a, b)
                    self.label.add("交集（不）为∅求参数范围")
                elif op == "∪" and right == "R" and isinstance(a, str) and isinstance(b, str):
                    self.label.add("并集为R求参数范围")
                    self.solve_unival(a, b)

        elif op == "=" and isinstance(left, list) and isinstance(right, dict):  # A∩B = {1, 2, 3}, C_{A}B = {x|x>1}

            if len(left) == 3:
                a, op, b = left
                if op == "∩":
                    self.solve_inter(a, b, right)
                    self.label.add("已知两集合交集求参数")
                elif op == '∪':
                    a = self.search(a)
                    b = self.search(b)
                    assert a and b, "set not found"
                    a_v = a.value["value"]

                    b_v = b.value["value"]
                    target = [sympify(v) for v in right["conditions"][0]['set']]
                    self.left = "".join(left)
                    self.right = str(right)
                    self.solve_list(list(set(a_v + b_v)), target, lambda aa, bb: aa == bb)
                    self.label.add("已知两集合并集求参数")
                else:
                    raise ValueError
            else:
                raise ValueError
        elif op == "=" and isinstance(left, str) and isinstance(right, list):
            self.solve_assign(left, right)

        elif op in ["⊆", "⊊"]:  # 已知集合A={x|a+1≤x≤2a-1},B={x|-2≤x≤5},若A⊆B,那么a的取值范围是.
            self.solve_child(left, right)
            self.label.add("集合包含关系求参数")
        elif op == "⊇":
            try:
                self.solve_child(right, left)
            except Exception:
                self.fake_return(right, left)

            self.label.add("集合包含关系求参数")
        elif op == "=" and isinstance(left, list) and isinstance(right, list):
            self.label.add("集合相等求参数－数组")
            try:
                self.left = str(left)
                self.right = str(right)
                self.solve_list(left, right, func=lambda aa, bb: aa == bb)
            except Exception:
                self.fake_return(left, right)
        elif op == "!=" and isinstance(left, list) and right in ["ph", "∅"]:  # M∩N≠∅
            self.label.add("交集（不）为∅求参数范围")
            return self.solve_unempty(left[0], left[-1])
        else:
            raise ValueError

        return self


if __name__ == '__main__':
    pass
